package asteroids.entity.equipment;

import java.awt.Color;
import java.util.ArrayList;

import org.apache.log4j.Logger;

import asteroids.engine.GameState;
import asteroids.engine.Utils;
import asteroids.entity.Asteroid;
import asteroids.entity.GameObject;
import asteroids.entity.Icing;
import asteroids.entity.Ship;
import asteroids.entity.weapon.Bullet;
import asteroids.entity.weapon.Weapon;

/**
 * Intended for use as a ship special. Launches small fighters which harass the
 * enemy. If no enemies are present, they will clean up smaller asteroids.
 * 
 * @author whitecd1
 * 
 */
public class LaunchFighters extends Weapon
{
    private static Logger logger = Logger.getLogger(LaunchFighters.class);

    private static final int ENERGY_COST = 8;
    private static final int REFIRE_DELAY = (int) ((Utils.FRAMES_PER_SECOND * 9d) / 24);

    private Utils.PolarCoordinate[] fighterShape;
    private final int fighterTTL = 15 * Utils.FRAMES_PER_SECOND;

    private FighterControl control;

    public LaunchFighters(Ship ship, Utils.PolarCoordinate[] fighterShape)
    {
        super(ship, ENERGY_COST, REFIRE_DELAY);
        if( fighterShape.length <= 20 )
        {
            this.fighterShape = fighterShape;
        }
        else
        {
            logger.trace("using polar circle as fighter shape");
            // this.fighterShape = fighterShape;
            this.fighterShape = Utils.createPolarCircle(1);
        }
        control = new FighterControl(ship);
        ship.equip(control);
    }

    public LaunchFighters(Ship ship)
    {
        this(ship, Utils.scalePolarShape(ship.getObjectShape(), 0.2));
    }

    public boolean FIRE(GameState state)
    {
        boolean launched = false;
        if( canFireYet(state) && ship.getIntegrity() > 2 )
        {
            manageFireControl(state); // reset firetime and modify energy
            ship.modifyIntegrityBy(-2); // subtract crew.
            Fighter f1 = new Fighter(ship.getXPosition(), ship.getYPosition(),
                    ship.getRotation() + 145, fighterTTL, fighterShape, ship);
            Fighter f2 = new Fighter(ship.getXPosition(), ship.getYPosition(),
                    ship.getRotation() + 215, fighterTTL, fighterShape, ship);
            control.launch(f1, f2);
        }
        return launched;
    }
}

/**
 * added by LaunchFighters to a ship, so that they can be updated every step?
 * alternately, just assign a target at launch, and return if the target gets
 * destroyed. maybe don't need a control Equipment?
 * 
 * @author whitecd1
 * 
 */
class FighterControl extends Equipment
{
    Logger logger = Logger.getLogger(FighterControl.class);

    private ArrayList<Fighter> fighters;

    public FighterControl(Ship ship)
    {
        super(ship);
        fighters = new ArrayList<Fighter>();
    }

    public void launch(Fighter f1, Fighter f2)
    {
        fighters.add(f1);
        ship.add(f1);
        if( f2 != null )
        {
            fighters.add(f2);
            ship.add(f2);
        }
    }

    public void update(GameState state)
    {
        for( Fighter f : fighters )
        {
            if( !f.hasTarget() )
            {
                // find a target to assign
                // prioritize ships
                /*
                 * ArrayList<? extends GameObject> ships =
                 * state.getShipManager().getObjects(); if( ships.size() > 1 ) {
                 * // pick nearest target Utils.sortByDistance(ships,
                 * Utils.createCartesianCoordinate(f.getXPosition(),
                 * f.getYPosition()));
                 * 
                 * int i = 0; // don't target owner while (ships.get(i) == ship
                 * && ships.size() > i) { i++; }
                 * 
                 * f.assignTarget(ships.get(i)); }
                 */
                if( !f.hasTarget() ) // didn't find a living ship to kill
                {
                    // if no ships, destroy small asteroids?
                    ArrayList<Asteroid> asteroids = state.getAsteroidManager().getObjects();
                    // there should always be asteroids available, yes?
                    if( asteroids.size() > 0 )
                    {
                        Utils.sortByDistance(asteroids,
                                Utils.createCartesianCoordinate(f.getXPosition(), f.getYPosition()));
                        int i = 0;
                        Asteroid target = asteroids.get(i);
                        while (target.getSize() > Asteroid.SMALL && asteroids.size() > i)
                        {
                            target = asteroids.get(i);
                            i++;
                        }
                        if( target.getSize() <= Asteroid.SMALL )
                        { // only shoot
                          // small-medium
                          // asteroids
                            f.assignTarget(target);
                        }
                    }
                }
            }
        }
    }

    @Override
    public void respawn()
    {
        for( Fighter f : fighters )
        {
            f.destroy();
        }
        fighters.clear();
    }
}

/**
 * The fighters could be modeled internally, but so that other ships can be
 * aware of their presence, they are sub-classed from Bullet and added to the
 * controlling Ship
 * 
 * @author whitecd1
 * 
 */
class Fighter extends Bullet
{
    private static final double fighter_speed = 150.0 / Utils.FRAMES_PER_SECOND;
    private static final int FIGHTER_INTEGRITY = 0; // die at the drop of a hat
    private static final int REFIRE_DELAY = (int) (Utils.FRAMES_PER_SECOND / 2.0);
    private long lastFireTime;
    private final int FIGHTER_TTL;
    private int fighter_ttl_left;
    private GameObject target = null;
    private Ship owner;
    private double firingRange = 10;
    private double optimalRange;

    private double launching;

    public Fighter(double x, double y, double direction, int TTL, Utils.PolarCoordinate[] shape,
            Ship owner)
    {
        super(x, y, direction, fighter_speed, FIGHTER_INTEGRITY, TTL, shape, owner);
        this.owner = owner;
        FIGHTER_TTL = TTL;
        fighter_ttl_left = FIGHTER_TTL;
        lastFireTime = FIGHTER_TTL + REFIRE_DELAY; // ensure it can shoot
                                                   // immediately
        launching = (int) (Utils.FRAMES_PER_SECOND * .25);
    }

    public void assignTarget(GameObject t)
    {
        target = t;
        optimalRange = target.getMaxRadius() + firingRange;
    }

    public boolean hasTarget()
    {
        return target != null && target.isAlive();
    }

    public void update()
    {
        if( !owner.isAlive() )
        {
            destroy();
        }
        fighter_ttl_left--;

        if( launching == 0 )
        {
            Utils.CartesianCoordinate direction;
            // if it has no target or target is dead, or TTL is low, go home
            if( target == null || !target.isAlive() || fighter_ttl_left <= FIGHTER_TTL / 2 )
            {
                if( Utils.gameObjectsCollide(this, owner) )
                {
                    owner.modifyIntegrityBy(1); // reintegrate pilot into ship
                                                // crew
                    destroy();
                }
                // return to owner
                rotation = Utils.pointDirection(x_position, y_position, owner.getXPosition(),
                        owner.getYPosition());
                direction = Utils.cartesianFromPolar(Utils.createPolarCoordinate(
                        Utils.pointDirection(x_position, y_position, owner.getXPosition(),
                                owner.getYPosition()), fighter_speed));
            }
            else
            // go hunting!
            {
                // try to get within range of the target, but not too close;
                if( Utils.pointDistance(x_position, y_position, target.getXPosition(),
                        target.getYPosition()) > optimalRange )
                {
                    // chase
                    rotation = Utils.pointDirection(x_position, y_position, target.getXPosition(),
                            target.getYPosition());
                    direction = Utils.cartesianFromPolar(Utils.createPolarCoordinate(rotation,
                            fighter_speed));
                }
                else
                {
                    // circle
                    rotation = Utils.pointDirection(x_position, y_position, target.getXPosition(),
                            target.getYPosition()) + 90;
                    direction = Utils.cartesianFromPolar(Utils.createPolarCoordinate(rotation,
                            fighter_speed));
                }

                if( Utils.pointDistance(x_position, y_position, target.getXPosition(),
                        target.getYPosition()) <= optimalRange + target.getMaxRadius()
                        && lastFireTime - fighter_ttl_left > REFIRE_DELAY )
                {
                    // shoot it!
                    lastFireTime = fighter_ttl_left;
                    Bullet zap = new Bullet(target.getXPosition(), target.getYPosition(), 0, 0, 1,
                            1, owner);
                    owner.add(zap);
                    // target.collide(zap);
                    // zap.destroy();
                    // zap = null;

                    Utils.CartesianCoordinate nmexy = Utils.createCartesianCoordinate(
                            target.getXPosition() - getXPosition(), target.getYPosition()
                                    - getYPosition());
                    Utils.PolarCoordinate[] zapShape = { Utils.createPolarCoordinate(0, 0),
                            Utils.polarFromCartesian(nmexy) };
                    Icing pew = new Icing(x_position, y_position, Utils.FRAMES_PER_SECOND / 12,
                            zapShape);
                    pew.setColor(Color.YELLOW);
                    icing.add(pew);
                }
            }
            x_speed = direction.getXComponent();
            y_speed = direction.getYComponent();
        }
        else
        {
            launching--;
        }
        Icing trail = new Icing(getXPosition(), getYPosition(), 4);
        trail.setColor(Color.GRAY);
        icing.add(trail);
        super.update();
    }

    // Fighters should not collide with friendlies
    @Override
    public void collide(GameObject other, double power)
    {
        if( !other.getAffiliation().equals(affiliation) )
        {
            super.collide(other, power);
        }
    }
}